package com.yida.common.core.ukey;

import java.math.BigInteger;

public abstract class ECFieldElement implements ECConstants {
    BigInteger x;
    BigInteger p;

    protected ECFieldElement(BigInteger q, BigInteger x) {
        if (x.compareTo(q) >= 0) {
            throw new IllegalArgumentException("x value too large in field element");
        }

        this.x = x;
        this.p = q; // curve.getQ();
    }

    public BigInteger toBigInteger() {
        return x;
    }

    public boolean equals(Object other) {
        if (other == this)
            return true;

        if (!(other instanceof ECFieldElement))
            return false;

        ECFieldElement o = (ECFieldElement)other;
        return p.equals(o.p) && x.equals(o.x);
    }

    public abstract String getFieldName();

    public abstract ECFieldElement add(ECFieldElement b);

    public abstract ECFieldElement subtract(ECFieldElement b);

    public abstract ECFieldElement multiply(ECFieldElement b);

    public abstract ECFieldElement divide(ECFieldElement b);

    public abstract ECFieldElement negate();

    public abstract ECFieldElement square();

    public abstract ECFieldElement invert();

    public abstract ECFieldElement sqrt();

    public static class Fp extends ECFieldElement {
        /**
         * return the field name for this field.
         *
         * @return the string "Fp".
         */
        public String getFieldName() {
            return "Fp";
        }

        public Fp(BigInteger q, BigInteger x) {
            super(q, x);
        }

        public ECFieldElement add(ECFieldElement b) {
            return new Fp(p, x.add(b.x).mod(p));
        }

        public ECFieldElement subtract(ECFieldElement b) {
            return new Fp(p, x.subtract(b.x).mod(p));
        }

        public ECFieldElement multiply(ECFieldElement b) {
            return new Fp(p, x.multiply(b.x).mod(p));
        }

        public ECFieldElement divide(ECFieldElement b) {
            return new Fp(p, x.multiply(b.x.modInverse(p)).mod(p));
        }

        public ECFieldElement negate() {
            return new Fp(p, x.negate().mod(p));
        }

        public ECFieldElement square() {
            return new Fp(p, x.multiply(x).mod(p));
        }

        public ECFieldElement invert() {
            return new Fp(p, x.modInverse(p));
        }

        // D.1.4 91
        /**
         * return a sqrt root - the routine verifies that the calculation returns the right value - if none exists it
         * returns null.
         */
        public ECFieldElement sqrt() {
            // p mod 4 == 3
            if (p.testBit(1)) {
                // z = g^(u+1) + p, p = 4u + 3
                ECFieldElement z = new Fp(p, x.modPow(p.shiftRight(2).add(ONE), p));

                return z.square().equals(this) ? z : null;
            }

            throw new RuntimeException("not done yet");
        }
    }
}
